/*
 * --------------------------------------
 * Benny Bottema -- WavSound Sound adaption
 * http://blog.projectnibble.org/
 * --------------------------------------
 * sazameki -- audio manipulating library
 * http://sazameki.org/
 * --------------------------------------
 *
 * - developed by:
 * 						Benny Bottema
 * 						blog.projectnibble.org
 *   hosted by:
 *  					Google Code (code.google.com)
 * 						code.google.com/p/as3wavsound/
 *
 * - audio library in its original state developed by:
 * 						Takaaki Yamazaki
 * 						www.zkdesign.jp
 *   hosted by:
 *  					Spark project (www.libspark.org)
 * 						www.libspark.org/svn/as3/sazameki/branches/fp10/
 */

/*
 * Licensed under the MIT License
 *
 * Copyright (c) 2008 Takaaki Yamazaki
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package org.as3wavsound {
    import flash.events.EventDispatcher;
    import flash.media.SoundChannel;
    import flash.events.Event;
    import flash.media.SoundTransform;
    import org.as3wavsound.sazameki.core.AudioSamples;
    import org.as3wavsound.WavSound;
    
    /**
     * Used to keep track of open channels during playback. Each channel represents
     * an 'instance' of a sound and so each channel is responsible for its own mixing.
     *
     * The WavSound class uses the WavSoundPlayer to play instances of itself and
     * returns the WavSoundChannel returned by the player.
     *
     * Also, the WavSoundPlayer uses the buffer() function to make the playing WavSoundChannel
     * mix its own samples into the master buffer.
     *
     * Dispatches the Event.SOUND_COMPLETE event when the last sample has been mixed
     * into the master buffer.
     *
     * Also see buffer().
     *
     * @author Benny Bottema
     */
    
    [Event(name="soundComplete", type="flash.events.Event")]
    
    public class WavSoundChannel extends EventDispatcher {
        
        /*
         * creation-time information
         */
        
        // The player to delegate play() stop() requests to.
        private var player:WavSoundPlayer;
        
        // a WavSound currently playing back on this channel instance 
        // (there can be mutliple instances with the same WavSound).
        private var _wavSound:WavSound;
        
        // works the same as Adobe's SoundChannel.soundTransform
        private var _soundTransform:SoundTransform = new SoundTransform();
        
        /*
         * play-time information *per WavSound instance*
         */
        
        // starting phase if not at the beginning, made global to avoid recalculating all the time
        private var startPhase:Number = 0; 
        // current phase of the sound, basically matches a single current sample frame for each WavSound
        private var phase:Number = 0;
        // the current avarage volume of samples buffered to the left audiochannel
        private var _leftPeak:Number = 0;
        // the current avarage volume of samples buffered to the right audiochannel
        private var _rightPeak:Number = 0;
        // how many loops we need to buffer
        private var loopsLeft:Number;
        // indicates if the phase has reached total sample count and no loops are left
        private var finished:Boolean;
        
        /**
         * Constructor: pre-calculates starting phase (and performs some validation for this), see init().
         */
        public function WavSoundChannel(player:WavSoundPlayer, wavSound:WavSound, startTime:Number, loops:int, soundTransform:SoundTransform) {
            this.player = player;
            this._wavSound = wavSound;
            if (soundTransform != null) {
                this._soundTransform = soundTransform;
            }
            init(startTime, loops);
        }
        
        /**
         * Calculates and validates the starting time. Starting time in milliseconds is converted into
         * sample position and then marked as starting phase.
         *
         * Also resets finished state and sets 'loopsLeft' equal to the given 'loops' value.
         */
        internal function init(startTime:Number, loops:int):void {
            var startPositionInMillis:Number = Math.floor(startTime);
            var maxPositionInMillis:Number = Math.floor(_wavSound.length);
            if (startPositionInMillis > maxPositionInMillis) {
                throw new Error("startTime greater than sound's length, max startTime is " + maxPositionInMillis);
            }
            phase = startPhase = Math.floor(startPositionInMillis * _wavSound.samples.length / _wavSound.length);
            finished = false;
            loopsLeft = loops;
        }
        
        /**
         * Tells the WavsoundPlayer to stop this specific SoundWavChannel instance.
         */		
        public function stop():void {
            player.stop(this);
        }
        
        /**
         * Called from WavSoundPlayer when the player is ready to mix new samples into the master
         * sample buffer.
         *
         * Fills a target samplebuffer with (optionally transformed) samples from the current
         * WavSoundChannel instance.
         *
         * Keeps filling the buffer until the last samples are buffered or until the buffersize is
         * reached. When the buffer is full, phase and loopsLeft keep track of how which samples
         * still need to be buffered in the next buffering cycle (when this method is called again).
         *
         * @param	sampleBuffer The target buffer to mix in the current (transformed) samples.
         * @param	soundTransform The soundtransform that belongs to a single channel being played
         * 			(containing volume, panning etc.).
         */	
        internal function buffer(sampleBuffer:AudioSamples):void {
            // calculate volume and panning
            var volume: Number = (_soundTransform.volume / 1);
            var volumeLeft: Number = volume * (1 - _soundTransform.pan) / 2;
            var volumeRight: Number = volume * (1 + _soundTransform.pan) / 2;
            // channel settings
            var needRightChannel:Boolean = _wavSound.playbackSettings.channels == 2;
            var hasRightChannel:Boolean = _wavSound.samples.setting.channels == 2;
            
            // extra references to avoid excessive getter calls in the following 
            // for-loop (it appeares CPU is being hogged otherwise)
            var samplesLength:Number = _wavSound.samples.length;
            var samplesLeft:Vector.<Number> = _wavSound.samples.left;
            var samplesRight:Vector.<Number> = _wavSound.samples.right;
            var sampleBufferLength:Number = sampleBuffer.length;
            var sampleBufferLeft:Vector.<Number> = sampleBuffer.left;
            var sampleBufferRight:Vector.<Number> = sampleBuffer.right;
            
            var leftPeakRecord:Number = 0;
            var rightPeakRecord:Number = 0;
            
            // finally, mix the samples in the master sample buffer
            if (!finished) {
                for (var i:int = 0; i < sampleBufferLength; i++) {
                    if (!finished) {					
                        // write (transformed) samples to buffer
                        var sampleLeft:Number = samplesLeft[phase] * volumeLeft;
                        sampleBufferLeft[i] += sampleLeft;
                        leftPeakRecord += sampleLeft;
                        var channelValue:Number = ((needRightChannel && hasRightChannel) ? samplesRight[phase] : samplesLeft[phase]);
                        var sampleRight:Number = channelValue * volumeRight;
                        sampleBufferRight[i] += sampleRight;
                        rightPeakRecord += sampleRight;
                        
                        // check playing and looping state
                        if (++phase >= samplesLength) {
                            phase = startPhase;
                            finished = loopsLeft-- == 0;
                        }
                    }
                }
                
                if (finished) {
                    dispatchEvent(new Event(Event.SOUND_COMPLETE));
                }
            }
            
            _leftPeak = leftPeakRecord / sampleBufferLength;
            _rightPeak = rightPeakRecord / sampleBufferLength
        }
        
        public function get leftPeak(): Number {
            return _leftPeak;
        }
        
        public function get rightPeak(): Number {
            return _rightPeak;
        }
        
        /**
         * Returns the current position in milliseconds:
         *
         * phase * wavSound.length / wavSound.samples.length
         */		 		
        public function get position(): Number {
            return phase * _wavSound.length / _wavSound.samples.length;
        }
        
        /**
         * Returns the soundTransform used during sample buffering. Can be modified to
         * influence sound in near real-time (such as volume, panning, etc.)
         */
        public function get soundTransform():SoundTransform {
            return _soundTransform;
        }
        
        /**
         * Replaces the current SoundTransform. Added to match Adobe's SoundChannel API.
         */
        public function set soundTransform(transform:SoundTransform):void {
            _soundTransform = transform;
        }
    }
}

